home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2000 March / maximum-cd-2000-03.iso / Quake3 Game Source / Q3AGameSource.exe / Main / ui_players.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-01-18  |  29.6 KB  |  1,229 lines

  1. // Copyright (C) 1999-2000 Id Software, Inc.
  2. //
  3. // ui_players.c
  4.  
  5. #include "ui_local.h"
  6.  
  7.  
  8. #define UI_TIMER_GESTURE        2300
  9. #define UI_TIMER_JUMP            1000
  10. #define UI_TIMER_LAND            130
  11. #define UI_TIMER_WEAPON_SWITCH    300
  12. #define UI_TIMER_ATTACK            500
  13. #define    UI_TIMER_MUZZLE_FLASH    20
  14. #define    UI_TIMER_WEAPON_DELAY    250
  15.  
  16. #define JUMP_HEIGHT                56
  17.  
  18. #define SWINGSPEED                0.3
  19.  
  20. #define SPIN_SPEED                0.9
  21. #define COAST_TIME                1000
  22.  
  23.  
  24. static int            dp_realtime;
  25. static float        jumpHeight;
  26.  
  27.  
  28. /*
  29. ===============
  30. UI_PlayerInfo_SetWeapon
  31. ===============
  32. */
  33. static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum ) {
  34.     gitem_t *    item;
  35.     char        path[MAX_QPATH];
  36.  
  37.     pi->currentWeapon = weaponNum;
  38. tryagain:
  39.     pi->realWeapon = weaponNum;
  40.     pi->weaponModel = 0;
  41.     pi->barrelModel = 0;
  42.     pi->flashModel = 0;
  43.  
  44.     if ( weaponNum == WP_NONE ) {
  45.         return;
  46.     }
  47.  
  48.     for ( item = bg_itemlist + 1; item->classname ; item++ ) {
  49.         if ( item->giType != IT_WEAPON ) {
  50.             continue;
  51.         }
  52.         if ( item->giTag == weaponNum ) {
  53.             break;
  54.         }
  55.     }
  56.  
  57.     if ( item->classname ) {
  58.         pi->weaponModel = trap_R_RegisterModel( item->world_model[0] );
  59.     }
  60.  
  61.     if( pi->weaponModel == 0 ) {
  62.         if( weaponNum == WP_MACHINEGUN ) {
  63.             weaponNum = WP_NONE;
  64.             goto tryagain;
  65.         }
  66.         weaponNum = WP_MACHINEGUN;
  67.         goto tryagain;
  68.     }
  69.  
  70.     if ( weaponNum == WP_MACHINEGUN || weaponNum == WP_GAUNTLET || weaponNum == WP_BFG ) {
  71.         strcpy( path, item->world_model[0] );
  72.         COM_StripExtension( path, path );
  73.         strcat( path, "_barrel.md3" );
  74.         pi->barrelModel = trap_R_RegisterModel( path );
  75.     }
  76.  
  77.     strcpy( path, item->world_model[0] );
  78.     COM_StripExtension( path, path );
  79.     strcat( path, "_flash.md3" );
  80.     pi->flashModel = trap_R_RegisterModel( path );
  81.  
  82.     switch( weaponNum ) {
  83.     case WP_GAUNTLET:
  84.         MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
  85.         break;
  86.  
  87.     case WP_MACHINEGUN:
  88.         MAKERGB( pi->flashDlightColor, 1, 1, 0 );
  89.         break;
  90.  
  91.     case WP_SHOTGUN:
  92.         MAKERGB( pi->flashDlightColor, 1, 1, 0 );
  93.         break;
  94.  
  95.     case WP_GRENADE_LAUNCHER:
  96.         MAKERGB( pi->flashDlightColor, 1, 0.7, 0.5 );
  97.         break;
  98.  
  99.     case WP_ROCKET_LAUNCHER:
  100.         MAKERGB( pi->flashDlightColor, 1, 0.75, 0 );
  101.         break;
  102.  
  103.     case WP_LIGHTNING:
  104.         MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
  105.         break;
  106.  
  107.     case WP_RAILGUN:
  108.         MAKERGB( pi->flashDlightColor, 1, 0.5, 0 );
  109.         break;
  110.  
  111.     case WP_PLASMAGUN:
  112.         MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
  113.         break;
  114.  
  115.     case WP_BFG:
  116.         MAKERGB( pi->flashDlightColor, 1, 0.7, 1 );
  117.         break;
  118.  
  119.     case WP_GRAPPLING_HOOK:
  120.         MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
  121.         break;
  122.  
  123.     default:
  124.         MAKERGB( pi->flashDlightColor, 1, 1, 1 );
  125.         break;
  126.     }
  127. }
  128.  
  129.  
  130. /*
  131. ===============
  132. UI_ForceLegsAnim
  133. ===============
  134. */
  135. static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) {
  136.     pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
  137.  
  138.     if ( anim == LEGS_JUMP ) {
  139.         pi->legsAnimationTimer = UI_TIMER_JUMP;
  140.     }
  141. }
  142.  
  143.  
  144. /*
  145. ===============
  146. UI_SetLegsAnim
  147. ===============
  148. */
  149. static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) {
  150.     if ( pi->pendingLegsAnim ) {
  151.         anim = pi->pendingLegsAnim;
  152.         pi->pendingLegsAnim = 0;
  153.     }
  154.     UI_ForceLegsAnim( pi, anim );
  155. }
  156.  
  157.  
  158. /*
  159. ===============
  160. UI_ForceTorsoAnim
  161. ===============
  162. */
  163. static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) {
  164.     pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
  165.  
  166.     if ( anim == TORSO_GESTURE ) {
  167.         pi->torsoAnimationTimer = UI_TIMER_GESTURE;
  168.     }
  169.  
  170.     if ( anim == TORSO_ATTACK || anim == TORSO_ATTACK2 ) {
  171.         pi->torsoAnimationTimer = UI_TIMER_ATTACK;
  172.     }
  173. }
  174.  
  175.  
  176. /*
  177. ===============
  178. UI_SetTorsoAnim
  179. ===============
  180. */
  181. static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) {
  182.     if ( pi->pendingTorsoAnim ) {
  183.         anim = pi->pendingTorsoAnim;
  184.         pi->pendingTorsoAnim = 0;
  185.     }
  186.  
  187.     UI_ForceTorsoAnim( pi, anim );
  188. }
  189.  
  190.  
  191. /*
  192. ===============
  193. UI_TorsoSequencing
  194. ===============
  195. */
  196. static void UI_TorsoSequencing( playerInfo_t *pi ) {
  197.     int        currentAnim;
  198.  
  199.     currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
  200.  
  201.     if ( pi->weapon != pi->currentWeapon ) {
  202.         if ( currentAnim != TORSO_DROP ) {
  203.             pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
  204.             UI_ForceTorsoAnim( pi, TORSO_DROP );
  205.         }
  206.     }
  207.  
  208.     if ( pi->torsoAnimationTimer > 0 ) {
  209.         return;
  210.     }
  211.  
  212.     if( currentAnim == TORSO_GESTURE ) {
  213.         UI_SetTorsoAnim( pi, TORSO_STAND );
  214.         return;
  215.     }
  216.  
  217.     if( currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2 ) {
  218.         UI_SetTorsoAnim( pi, TORSO_STAND );
  219.         return;
  220.     }
  221.  
  222.     if ( currentAnim == TORSO_DROP ) {
  223.         UI_PlayerInfo_SetWeapon( pi, pi->weapon );
  224.         pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
  225.         UI_ForceTorsoAnim( pi, TORSO_RAISE );
  226.         return;
  227.     }
  228.  
  229.     if ( currentAnim == TORSO_RAISE ) {
  230.         UI_SetTorsoAnim( pi, TORSO_STAND );
  231.         return;
  232.     }
  233. }
  234.  
  235.  
  236. /*
  237. ===============
  238. UI_LegsSequencing
  239. ===============
  240. */
  241. static void UI_LegsSequencing( playerInfo_t *pi ) {
  242.     int        currentAnim;
  243.  
  244.     currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
  245.  
  246.     if ( pi->legsAnimationTimer > 0 ) {
  247.         if ( currentAnim == LEGS_JUMP ) {
  248.             jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP );
  249.         }
  250.         return;
  251.     }
  252.  
  253.     if ( currentAnim == LEGS_JUMP ) {
  254.         UI_ForceLegsAnim( pi, LEGS_LAND );
  255.         pi->legsAnimationTimer = UI_TIMER_LAND;
  256.         jumpHeight = 0;
  257.         return;
  258.     }
  259.  
  260.     if ( currentAnim == LEGS_LAND ) {
  261.         UI_SetLegsAnim( pi, LEGS_IDLE );
  262.         return;
  263.     }
  264. }
  265.  
  266.  
  267. /*
  268. ======================
  269. UI_PositionEntityOnTag
  270. ======================
  271. */
  272. static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 
  273.                             clipHandle_t parentModel, char *tagName ) {
  274.     int                i;
  275.     orientation_t    lerped;
  276.     
  277.     // lerp the tag
  278.     trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
  279.         1.0 - parent->backlerp, tagName );
  280.  
  281.     // FIXME: allow origin offsets along tag?
  282.     VectorCopy( parent->origin, entity->origin );
  283.     for ( i = 0 ; i < 3 ; i++ ) {
  284.         VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
  285.     }
  286.  
  287.     // cast away const because of compiler problems
  288.     MatrixMultiply( lerped.axis, ((refEntity_t*)parent)->axis, entity->axis );
  289.     entity->backlerp = parent->backlerp;
  290. }
  291.  
  292.  
  293. /*
  294. ======================
  295. UI_PositionRotatedEntityOnTag
  296. ======================
  297. */
  298. static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 
  299.                             clipHandle_t parentModel, char *tagName ) {
  300.     int                i;
  301.     orientation_t    lerped;
  302.     vec3_t            tempAxis[3];
  303.  
  304.     // lerp the tag
  305.     trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
  306.         1.0 - parent->backlerp, tagName );
  307.  
  308.     // FIXME: allow origin offsets along tag?
  309.     VectorCopy( parent->origin, entity->origin );
  310.     for ( i = 0 ; i < 3 ; i++ ) {
  311.         VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
  312.     }
  313.  
  314.     // cast away const because of compiler problems
  315.     MatrixMultiply( entity->axis, ((refEntity_t *)parent)->axis, tempAxis );
  316.     MatrixMultiply( lerped.axis, tempAxis, entity->axis );
  317. }
  318.  
  319.  
  320. /*
  321. ===============
  322. UI_SetLerpFrameAnimation
  323. ===============
  324. */
  325. static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
  326.     animation_t    *anim;
  327.  
  328.     lf->animationNumber = newAnimation;
  329.     newAnimation &= ~ANIM_TOGGLEBIT;
  330.  
  331.     if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS ) {
  332.         trap_Error( va("Bad animation number: %i", newAnimation) );
  333.     }
  334.  
  335.     anim = &ci->animations[ newAnimation ];
  336.  
  337.     lf->animation = anim;
  338.     lf->animationTime = lf->frameTime + anim->initialLerp;
  339. }
  340.  
  341.  
  342. /*
  343. ===============
  344. UI_RunLerpFrame
  345. ===============
  346. */
  347. static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
  348.     int            f;
  349.     animation_t    *anim;
  350.  
  351.     // see if the animation sequence is switching
  352.     if ( newAnimation != lf->animationNumber || !lf->animation ) {
  353.         UI_SetLerpFrameAnimation( ci, lf, newAnimation );
  354.     }
  355.  
  356.     // if we have passed the current frame, move it to
  357.     // oldFrame and calculate a new frame
  358.     if ( dp_realtime >= lf->frameTime ) {
  359.         lf->oldFrame = lf->frame;
  360.         lf->oldFrameTime = lf->frameTime;
  361.  
  362.         // get the next frame based on the animation
  363.         anim = lf->animation;
  364.         if ( dp_realtime < lf->animationTime ) {
  365.             lf->frameTime = lf->animationTime;        // initial lerp
  366.         } else {
  367.             lf->frameTime = lf->oldFrameTime + anim->frameLerp;
  368.         }
  369.         f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
  370.         if ( f >= anim->numFrames ) {
  371.             f -= anim->numFrames;
  372.             if ( anim->loopFrames ) {
  373.                 f %= anim->loopFrames;
  374.                 f += anim->numFrames - anim->loopFrames;
  375.             } else {
  376.                 f = anim->numFrames - 1;
  377.                 // the animation is stuck at the end, so it
  378.                 // can immediately transition to another sequence
  379.                 lf->frameTime = dp_realtime;
  380.             }
  381.         }
  382.         lf->frame = anim->firstFrame + f;
  383.         if ( dp_realtime > lf->frameTime ) {
  384.             lf->frameTime = dp_realtime;
  385.         }
  386.     }
  387.  
  388.     if ( lf->frameTime > dp_realtime + 200 ) {
  389.         lf->frameTime = dp_realtime;
  390.     }
  391.  
  392.     if ( lf->oldFrameTime > dp_realtime ) {
  393.         lf->oldFrameTime = dp_realtime;
  394.     }
  395.     // calculate current lerp value
  396.     if ( lf->frameTime == lf->oldFrameTime ) {
  397.         lf->backlerp = 0;
  398.     } else {
  399.         lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
  400.     }
  401. }
  402.  
  403.  
  404. /*
  405. ===============
  406. UI_PlayerAnimation
  407. ===============
  408. */
  409. static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp,
  410.                         int *torsoOld, int *torso, float *torsoBackLerp ) {
  411.  
  412.     // legs animation
  413.     pi->legsAnimationTimer -= uis.frametime;
  414.     if ( pi->legsAnimationTimer < 0 ) {
  415.         pi->legsAnimationTimer = 0;
  416.     }
  417.  
  418.     UI_LegsSequencing( pi );
  419.  
  420.     if ( pi->legs.yawing && ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) {
  421.         UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN );
  422.     } else {
  423.         UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim );
  424.     }
  425.     *legsOld = pi->legs.oldFrame;
  426.     *legs = pi->legs.frame;
  427.     *legsBackLerp = pi->legs.backlerp;
  428.  
  429.     // torso animation
  430.     pi->torsoAnimationTimer -= uis.frametime;
  431.     if ( pi->torsoAnimationTimer < 0 ) {
  432.         pi->torsoAnimationTimer = 0;
  433.     }
  434.  
  435.     UI_TorsoSequencing( pi );
  436.  
  437.     UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim );
  438.     *torsoOld = pi->torso.oldFrame;
  439.     *torso = pi->torso.frame;
  440.     *torsoBackLerp = pi->torso.backlerp;
  441. }
  442.  
  443.  
  444. /*
  445. ==================
  446. UI_SwingAngles
  447. ==================
  448. */
  449. static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance,
  450.                     float speed, float *angle, qboolean *swinging ) {
  451.     float    swing;
  452.     float    move;
  453.     float    scale;
  454.  
  455.     if ( !*swinging ) {
  456.         // see if a swing should be started
  457.         swing = AngleSubtract( *angle, destination );
  458.         if ( swing > swingTolerance || swing < -swingTolerance ) {
  459.             *swinging = qtrue;
  460.         }
  461.     }
  462.  
  463.     if ( !*swinging ) {
  464.         return;
  465.     }
  466.     
  467.     // modify the speed depending on the delta
  468.     // so it doesn't seem so linear
  469.     swing = AngleSubtract( destination, *angle );
  470.     scale = fabs( swing );
  471.     if ( scale < swingTolerance * 0.5 ) {
  472.         scale = 0.5;
  473.     } else if ( scale < swingTolerance ) {
  474.         scale = 1.0;
  475.     } else {
  476.         scale = 2.0;
  477.     }
  478.  
  479.     // swing towards the destination angle
  480.     if ( swing >= 0 ) {
  481.         move = uis.frametime * scale * speed;
  482.         if ( move >= swing ) {
  483.             move = swing;
  484.             *swinging = qfalse;
  485.         }
  486.         *angle = AngleMod( *angle + move );
  487.     } else if ( swing < 0 ) {
  488.         move = uis.frametime * scale * -speed;
  489.         if ( move <= swing ) {
  490.             move = swing;
  491.             *swinging = qfalse;
  492.         }
  493.         *angle = AngleMod( *angle + move );
  494.     }
  495.  
  496.     // clamp to no more than tolerance
  497.     swing = AngleSubtract( destination, *angle );
  498.     if ( swing > clampTolerance ) {
  499.         *angle = AngleMod( destination - (clampTolerance - 1) );
  500.     } else if ( swing < -clampTolerance ) {
  501.         *angle = AngleMod( destination + (clampTolerance - 1) );
  502.     }
  503. }
  504.  
  505.  
  506. /*
  507. ======================
  508. UI_MovedirAdjustment
  509. ======================
  510. */
  511. static float UI_MovedirAdjustment( playerInfo_t *pi ) {
  512.     vec3_t        relativeAngles;
  513.     vec3_t        moveVector;
  514.  
  515.     VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles );
  516.     AngleVectors( relativeAngles, moveVector, NULL, NULL );
  517.     if ( Q_fabs( moveVector[0] ) < 0.01 ) {
  518.         moveVector[0] = 0.0;
  519.     }
  520.     if ( Q_fabs( moveVector[1] ) < 0.01 ) {
  521.         moveVector[1] = 0.0;
  522.     }
  523.  
  524.     if ( moveVector[1] == 0 && moveVector[0] > 0 ) {
  525.         return 0;
  526.     }
  527.     if ( moveVector[1] < 0 && moveVector[0] > 0 ) {
  528.         return 22;
  529.     }
  530.     if ( moveVector[1] < 0 && moveVector[0] == 0 ) {
  531.         return 45;
  532.     }
  533.     if ( moveVector[1] < 0 && moveVector[0] < 0 ) {
  534.         return -22;
  535.     }
  536.     if ( moveVector[1] == 0 && moveVector[0] < 0 ) {
  537.         return 0;
  538.     }
  539.     if ( moveVector[1] > 0 && moveVector[0] < 0 ) {
  540.         return 22;
  541.     }
  542.     if ( moveVector[1] > 0 && moveVector[0] == 0 ) {
  543.         return  -45;
  544.     }
  545.  
  546.     return -22;
  547. }
  548.  
  549.  
  550. /*
  551. ===============
  552. UI_PlayerAngles
  553. ===============
  554. */
  555. static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) {
  556.     vec3_t        legsAngles, torsoAngles, headAngles;
  557.     float        dest;
  558.     float        adjust;
  559.  
  560.     VectorCopy( pi->viewAngles, headAngles );
  561.     headAngles[YAW] = AngleMod( headAngles[YAW] );
  562.     VectorClear( legsAngles );
  563.     VectorClear( torsoAngles );
  564.  
  565.     // --------- yaw -------------
  566.  
  567.     // allow yaw to drift a bit
  568.     if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE 
  569.         || ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND  ) {
  570.         // if not standing still, always point all in the same direction
  571.         pi->torso.yawing = qtrue;    // always center
  572.         pi->torso.pitching = qtrue;    // always center
  573.         pi->legs.yawing = qtrue;    // always center
  574.     }
  575.  
  576.     // adjust legs for movement dir
  577.     adjust = UI_MovedirAdjustment( pi );
  578.     legsAngles[YAW] = headAngles[YAW] + adjust;
  579.     torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust;
  580.  
  581.  
  582.     // torso
  583.     UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing );
  584.     UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing );
  585.  
  586.     torsoAngles[YAW] = pi->torso.yawAngle;
  587.     legsAngles[YAW] = pi->legs.yawAngle;
  588.  
  589.     // --------- pitch -------------
  590.  
  591.     // only show a fraction of the pitch angle in the torso
  592.     if ( headAngles[PITCH] > 180 ) {
  593.         dest = (-360 + headAngles[PITCH]) * 0.75;
  594.     } else {
  595.         dest = headAngles[PITCH] * 0.75;
  596.     }
  597.     UI_SwingAngles( dest, 15, 30, 0.1, &pi->torso.pitchAngle, &pi->torso.pitching );
  598.     torsoAngles[PITCH] = pi->torso.pitchAngle;
  599.  
  600.     // pull the angles back out of the hierarchial chain
  601.     AnglesSubtract( headAngles, torsoAngles, headAngles );
  602.     AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
  603.     AnglesToAxis( legsAngles, legs );
  604.     AnglesToAxis( torsoAngles, torso );
  605.     AnglesToAxis( headAngles, head );
  606. }
  607.  
  608.  
  609. /*
  610. ===============
  611. UI_PlayerFloatSprite
  612. ===============
  613. */
  614. static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) {
  615.     refEntity_t        ent;
  616.  
  617.     memset( &ent, 0, sizeof( ent ) );
  618.     VectorCopy( origin, ent.origin );
  619.     ent.origin[2] += 48;
  620.     ent.reType = RT_SPRITE;
  621.     ent.customShader = shader;
  622.     ent.radius = 10;
  623.     ent.renderfx = 0;
  624.     trap_R_AddRefEntityToScene( &ent );
  625. }
  626.  
  627.  
  628. /*
  629. ======================
  630. UI_MachinegunSpinAngle
  631. ======================
  632. */
  633. float    UI_MachinegunSpinAngle( playerInfo_t *pi ) {
  634.     int        delta;
  635.     float    angle;
  636.     float    speed;
  637.     int        torsoAnim;
  638.  
  639.     delta = dp_realtime - pi->barrelTime;
  640.     if ( pi->barrelSpinning ) {
  641.         angle = pi->barrelAngle + delta * SPIN_SPEED;
  642.     } else {
  643.         if ( delta > COAST_TIME ) {
  644.             delta = COAST_TIME;
  645.         }
  646.  
  647.         speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
  648.         angle = pi->barrelAngle + delta * speed;
  649.     }
  650.  
  651.     torsoAnim = pi->torsoAnim  & ~ANIM_TOGGLEBIT;
  652.     if( torsoAnim == TORSO_ATTACK2 ) {
  653.         torsoAnim = TORSO_ATTACK;
  654.     }
  655.     if ( pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK) ) {
  656.         pi->barrelTime = dp_realtime;
  657.         pi->barrelAngle = AngleMod( angle );
  658.         pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK);
  659.     }
  660.  
  661.     return angle;
  662. }
  663.  
  664.  
  665. /*
  666. ===============
  667. UI_DrawPlayer
  668. ===============
  669. */
  670. void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) {
  671.     refdef_t        refdef;
  672.     refEntity_t        legs;
  673.     refEntity_t        torso;
  674.     refEntity_t        head;
  675.     refEntity_t        gun;
  676.     refEntity_t        barrel;
  677.     refEntity_t        flash;
  678.     vec3_t            origin;
  679.     int                renderfx;
  680.     vec3_t            mins = {-16, -16, -24};
  681.     vec3_t            maxs = {16, 16, 32};
  682.     float            len;
  683.     float            xx;
  684.  
  685.     if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) {
  686.         return;
  687.     }
  688.  
  689.     dp_realtime = time;
  690.  
  691.     if ( pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer ) {
  692.         pi->weapon = pi->pendingWeapon;
  693.         pi->lastWeapon = pi->pendingWeapon;
  694.         pi->pendingWeapon = -1;
  695.         pi->weaponTimer = 0;
  696.         if( pi->currentWeapon != pi->weapon ) {
  697.             trap_S_StartLocalSound( trap_S_RegisterSound( "sound/weapons/change.wav" ), CHAN_LOCAL );
  698.         }
  699.     }
  700.  
  701.     UI_AdjustFrom640( &x, &y, &w, &h );
  702.  
  703.     y -= jumpHeight;
  704.  
  705.     memset( &refdef, 0, sizeof( refdef ) );
  706.     memset( &legs, 0, sizeof(legs) );
  707.     memset( &torso, 0, sizeof(torso) );
  708.     memset( &head, 0, sizeof(head) );
  709.  
  710.     refdef.rdflags = RDF_NOWORLDMODEL;
  711.  
  712.     AxisClear( refdef.viewaxis );
  713.  
  714.     refdef.x = x;
  715.     refdef.y = y;
  716.     refdef.width = w;
  717.     refdef.height = h;
  718.  
  719.     refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
  720.     xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
  721.     refdef.fov_y = atan2( refdef.height, xx );
  722.     refdef.fov_y *= ( 360 / M_PI );
  723.  
  724.     // calculate distance so the player nearly fills the box
  725.     len = 0.7 * ( maxs[2] - mins[2] );        
  726.     origin[0] = len / tan( DEG2RAD(refdef.fov_x) * 0.5 );
  727.     origin[1] = 0.5 * ( mins[1] + maxs[1] );
  728.     origin[2] = -0.5 * ( mins[2] + maxs[2] );
  729.  
  730.     refdef.time = dp_realtime;
  731.  
  732.     trap_R_ClearScene();
  733.  
  734.     // get the rotation information
  735.     UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis );
  736.     
  737.     // get the animation state (after rotation, to allow feet shuffle)
  738.     UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp,
  739.          &torso.oldframe, &torso.frame, &torso.backlerp );
  740.  
  741.     renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
  742.  
  743.     //
  744.     // add the legs
  745.     //
  746.     legs.hModel = pi->legsModel;
  747.     legs.customSkin = pi->legsSkin;
  748.  
  749.     VectorCopy( origin, legs.origin );
  750.  
  751.     VectorCopy( origin, legs.lightingOrigin );
  752.     legs.renderfx = renderfx;
  753.     VectorCopy (legs.origin, legs.oldorigin);
  754.  
  755.     trap_R_AddRefEntityToScene( &legs );
  756.  
  757.     if (!legs.hModel) {
  758.         return;
  759.     }
  760.  
  761.     //
  762.     // add the torso
  763.     //
  764.     torso.hModel = pi->torsoModel;
  765.     if (!torso.hModel) {
  766.         return;
  767.     }
  768.  
  769.     torso.customSkin = pi->torsoSkin;
  770.  
  771.     VectorCopy( origin, torso.lightingOrigin );
  772.  
  773.     UI_PositionRotatedEntityOnTag( &torso, &legs, pi->legsModel, "tag_torso");
  774.  
  775.     torso.renderfx = renderfx;
  776.  
  777.     trap_R_AddRefEntityToScene( &torso );
  778.  
  779.     //
  780.     // add the head
  781.     //
  782.     head.hModel = pi->headModel;
  783.     if (!head.hModel) {
  784.         return;
  785.     }
  786.     head.customSkin = pi->headSkin;
  787.  
  788.     VectorCopy( origin, head.lightingOrigin );
  789.  
  790.     UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, "tag_head");
  791.  
  792.     head.renderfx = renderfx;
  793.  
  794.     trap_R_AddRefEntityToScene( &head );
  795.  
  796.     //
  797.     // add the gun
  798.     //
  799.     if ( pi->currentWeapon != WP_NONE ) {
  800.         memset( &gun, 0, sizeof(gun) );
  801.         gun.hModel = pi->weaponModel;
  802.         VectorCopy( origin, gun.lightingOrigin );
  803.         UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon");
  804.         gun.renderfx = renderfx;
  805.         trap_R_AddRefEntityToScene( &gun );
  806.     }
  807.  
  808.     //
  809.     // add the spinning barrel
  810.     //
  811.     if ( pi->realWeapon == WP_MACHINEGUN || pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) {
  812.         vec3_t    angles;
  813.  
  814.         memset( &barrel, 0, sizeof(barrel) );
  815.         VectorCopy( origin, barrel.lightingOrigin );
  816.         barrel.renderfx = renderfx;
  817.  
  818.         barrel.hModel = pi->barrelModel;
  819.         angles[YAW] = 0;
  820.         angles[PITCH] = 0;
  821.         angles[ROLL] = UI_MachinegunSpinAngle( pi );
  822.         if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) {
  823.             angles[PITCH] = angles[ROLL];
  824.             angles[ROLL] = 0;
  825.         }
  826.         AnglesToAxis( angles, barrel.axis );
  827.  
  828.         UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel");
  829.  
  830.         trap_R_AddRefEntityToScene( &barrel );
  831.     }
  832.  
  833.     //
  834.     // add muzzle flash
  835.     //
  836.     if ( dp_realtime <= pi->muzzleFlashTime ) {
  837.         if ( pi->flashModel ) {
  838.             memset( &flash, 0, sizeof(flash) );
  839.             flash.hModel = pi->flashModel;
  840.             VectorCopy( origin, flash.lightingOrigin );
  841.             UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "tag_flash");
  842.             flash.renderfx = renderfx;
  843.             trap_R_AddRefEntityToScene( &flash );
  844.         }
  845.  
  846.         // make a dlight for the flash
  847.         if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) {
  848.             trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), pi->flashDlightColor[0],
  849.                 pi->flashDlightColor[1], pi->flashDlightColor[2] );
  850.         }
  851.     }
  852.  
  853.     //
  854.     // add the chat icon
  855.     //
  856.     if ( pi->chat ) {
  857.         UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) );
  858.     }
  859.  
  860.     //
  861.     // add an accent light
  862.     //
  863.     origin[0] -= 100;    // + = behind, - = in front
  864.     origin[1] += 100;    // + = left, - = right
  865.     origin[2] += 100;    // + = above, - = below
  866.     trap_R_AddLightToScene( origin, 500, 1.0, 1.0, 1.0 );
  867.  
  868.     origin[0] -= 100;
  869.     origin[1] -= 100;
  870.     origin[2] -= 100;
  871.     trap_R_AddLightToScene( origin, 500, 1.0, 0.0, 0.0 );
  872.  
  873.     trap_R_RenderScene( &refdef );
  874. }
  875.  
  876.  
  877. /*
  878. ==========================
  879. UI_RegisterClientSkin
  880. ==========================
  881. */
  882. static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName ) {
  883.     char        filename[MAX_QPATH];
  884.  
  885.     Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName );
  886.     pi->legsSkin = trap_R_RegisterSkin( filename );
  887.  
  888.     Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName );
  889.     pi->torsoSkin = trap_R_RegisterSkin( filename );
  890.  
  891.     Com_sprintf( filename, sizeof( filename ), "models/players/%s/head_%s.skin", modelName, skinName );
  892.     pi->headSkin = trap_R_RegisterSkin( filename );
  893.  
  894.     if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) {
  895.         return qfalse;
  896.     }
  897.  
  898.     return qtrue;
  899. }
  900.  
  901.  
  902. /*
  903. ======================
  904. UI_ParseAnimationFile
  905. ======================
  906. */
  907. static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) {
  908.     char        *text_p, *prev;
  909.     int            len;
  910.     int            i;
  911.     char        *token;
  912.     float        fps;
  913.     int            skip;
  914.     char        text[20000];
  915.     fileHandle_t    f;
  916.  
  917.     memset( animations, 0, sizeof( animation_t ) * MAX_ANIMATIONS );
  918.  
  919.     // load the file
  920.     len = trap_FS_FOpenFile( filename, &f, FS_READ );
  921.     if ( len <= 0 ) {
  922.         return qfalse;
  923.     }
  924.     if ( len >= ( sizeof( text ) - 1 ) ) {
  925.         Com_Printf( "File %s too long\n", filename );
  926.         return qfalse;
  927.     }
  928.     trap_FS_Read( text, len, f );
  929.     text[len] = 0;
  930.     trap_FS_FCloseFile( f );
  931.  
  932.     // parse the text
  933.     text_p = text;
  934.     skip = 0;    // quite the compiler warning
  935.  
  936.     // read optional parameters
  937.     while ( 1 ) {
  938.         prev = text_p;    // so we can unget
  939.         token = COM_Parse( &text_p );
  940.         if ( !token ) {
  941.             break;
  942.         }
  943.         if ( !Q_stricmp( token, "footsteps" ) ) {
  944.             token = COM_Parse( &text_p );
  945.             if ( !token ) {
  946.                 break;
  947.             }
  948.             continue;
  949.         } else if ( !Q_stricmp( token, "headoffset" ) ) {
  950.             for ( i = 0 ; i < 3 ; i++ ) {
  951.                 token = COM_Parse( &text_p );
  952.                 if ( !token ) {
  953.                     break;
  954.                 }
  955.             }
  956.             continue;
  957.         } else if ( !Q_stricmp( token, "sex" ) ) {
  958.             token = COM_Parse( &text_p );
  959.             if ( !token ) {
  960.                 break;
  961.             }
  962.             continue;
  963.         }
  964.  
  965.         // if it is a number, start parsing animations
  966.         if ( token[0] >= '0' && token[0] <= '9' ) {
  967.             text_p = prev;    // unget the token
  968.             break;
  969.         }
  970.  
  971.         Com_Printf( "unknown token '%s' is %s\n", token, filename );
  972.     }
  973.  
  974.     // read information for each frame
  975.     for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) {
  976.  
  977.         token = COM_Parse( &text_p );
  978.         if ( !token ) {
  979.             break;
  980.         }
  981.         animations[i].firstFrame = atoi( token );
  982.         // leg only frames are adjusted to not count the upper body only frames
  983.         if ( i == LEGS_WALKCR ) {
  984.             skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame;
  985.         }
  986.         if ( i >= LEGS_WALKCR ) {
  987.             animations[i].firstFrame -= skip;
  988.         }
  989.  
  990.         token = COM_Parse( &text_p );
  991.         if ( !token ) {
  992.             break;
  993.         }
  994.         animations[i].numFrames = atoi( token );
  995.  
  996.         token = COM_Parse( &text_p );
  997.         if ( !token ) {
  998.             break;
  999.         }
  1000.         animations[i].loopFrames = atoi( token );
  1001.  
  1002.         token = COM_Parse( &text_p );
  1003.         if ( !token ) {
  1004.             break;
  1005.         }
  1006.         fps = atof( token );
  1007.         if ( fps == 0 ) {
  1008.             fps = 1;
  1009.         }
  1010.         animations[i].frameLerp = 1000 / fps;
  1011.         animations[i].initialLerp = 1000 / fps;
  1012.     }
  1013.  
  1014.     if ( i != MAX_ANIMATIONS ) {
  1015.         Com_Printf( "Error parsing animation file: %s", filename );
  1016.         return qfalse;
  1017.     }
  1018.  
  1019.     return qtrue;
  1020. }
  1021.  
  1022.  
  1023. /*
  1024. ==========================
  1025. UI_RegisterClientModelname
  1026. ==========================
  1027. */
  1028. qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName ) {
  1029.     char        modelName[MAX_QPATH];
  1030.     char        skinName[MAX_QPATH];
  1031.     char        filename[MAX_QPATH];
  1032.     char        *slash;
  1033.  
  1034.     pi->torsoModel = 0;
  1035.     pi->headModel = 0;
  1036.  
  1037.     if ( !modelSkinName[0] ) {
  1038.         return qfalse;
  1039.     }
  1040.  
  1041.     Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) );
  1042.  
  1043.     slash = strchr( modelName, '/' );
  1044.     if ( !slash ) {
  1045.         // modelName did not include a skin name
  1046.         Q_strncpyz( skinName, "default", sizeof( skinName ) );
  1047.     } else {
  1048.         Q_strncpyz( skinName, slash + 1, sizeof( skinName ) );
  1049.         // truncate modelName
  1050.         *slash = 0;
  1051.     }
  1052.  
  1053.     // load cmodels before models so filecache works
  1054.  
  1055.     Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName );
  1056.     pi->legsModel = trap_R_RegisterModel( filename );
  1057.     if ( !pi->legsModel ) {
  1058.         Com_Printf( "Failed to load model file %s\n", filename );
  1059.         return qfalse;
  1060.     }
  1061.  
  1062.     Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName );
  1063.     pi->torsoModel = trap_R_RegisterModel( filename );
  1064.     if ( !pi->torsoModel ) {
  1065.         Com_Printf( "Failed to load model file %s\n", filename );
  1066.         return qfalse;
  1067.     }
  1068.  
  1069.     Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", modelName );
  1070.     pi->headModel = trap_R_RegisterModel( filename );
  1071.     if ( !pi->headModel ) {
  1072.         Com_Printf( "Failed to load model file %s\n", filename );
  1073.         return qfalse;
  1074.     }
  1075.  
  1076.     // if any skins failed to load, fall back to default
  1077.     if ( !UI_RegisterClientSkin( pi, modelName, skinName ) ) {
  1078.         if ( !UI_RegisterClientSkin( pi, modelName, "default" ) ) {
  1079.             Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName );
  1080.             return qfalse;
  1081.         }
  1082.     }
  1083.  
  1084.     // load the animations
  1085.     Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName );
  1086.     if ( !UI_ParseAnimationFile( filename, pi->animations ) ) {
  1087.         Com_Printf( "Failed to load animation file %s\n", filename );
  1088.         return qfalse;
  1089.     }
  1090.  
  1091.     return qtrue;
  1092. }
  1093.  
  1094.  
  1095. /*
  1096. ===============
  1097. UI_PlayerInfo_SetModel
  1098. ===============
  1099. */
  1100. void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model ) {
  1101.     memset( pi, 0, sizeof(*pi) );
  1102.     UI_RegisterClientModelname( pi, model );
  1103.     pi->weapon = WP_MACHINEGUN;
  1104.     pi->currentWeapon = pi->weapon;
  1105.     pi->lastWeapon = pi->weapon;
  1106.     pi->pendingWeapon = -1;
  1107.     pi->weaponTimer = 0;
  1108.     pi->chat = qfalse;
  1109.     pi->newModel = qtrue;
  1110.     UI_PlayerInfo_SetWeapon( pi, pi->weapon );
  1111. }
  1112.  
  1113.  
  1114. /*
  1115. ===============
  1116. UI_PlayerInfo_SetInfo
  1117. ===============
  1118. */
  1119. void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) {
  1120.     int            currentAnim;
  1121.     weapon_t    weaponNum;
  1122.  
  1123.     pi->chat = chat;
  1124.  
  1125.     // view angles
  1126.     VectorCopy( viewAngles, pi->viewAngles );
  1127.  
  1128.     // move angles
  1129.     VectorCopy( moveAngles, pi->moveAngles );
  1130.  
  1131.     if ( pi->newModel ) {
  1132.         pi->newModel = qfalse;
  1133.  
  1134.         jumpHeight = 0;
  1135.         pi->pendingLegsAnim = 0;
  1136.         UI_ForceLegsAnim( pi, legsAnim );
  1137.         pi->legs.yawAngle = viewAngles[YAW];
  1138.         pi->legs.yawing = qfalse;
  1139.  
  1140.         pi->pendingTorsoAnim = 0;
  1141.         UI_ForceTorsoAnim( pi, torsoAnim );
  1142.         pi->torso.yawAngle = viewAngles[YAW];
  1143.         pi->torso.yawing = qfalse;
  1144.  
  1145.         if ( weaponNumber != -1 ) {
  1146.             pi->weapon = weaponNumber;
  1147.             pi->currentWeapon = weaponNumber;
  1148.             pi->lastWeapon = weaponNumber;
  1149.             pi->pendingWeapon = -1;
  1150.             pi->weaponTimer = 0;
  1151.             UI_PlayerInfo_SetWeapon( pi, pi->weapon );
  1152.         }
  1153.  
  1154.         return;
  1155.     }
  1156.  
  1157.     // weapon
  1158.     if ( weaponNumber == -1 ) {
  1159.         pi->pendingWeapon = -1;
  1160.         pi->weaponTimer = 0;
  1161.     }
  1162.     else if ( weaponNumber != WP_NONE ) {
  1163.         pi->pendingWeapon = weaponNumber;
  1164.         pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY;
  1165.     }
  1166.     weaponNum = pi->lastWeapon;
  1167.     pi->weapon = weaponNum;
  1168.  
  1169.     if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) {
  1170.         torsoAnim = legsAnim = BOTH_DEATH1;
  1171.         pi->weapon = pi->currentWeapon = WP_NONE;
  1172.         UI_PlayerInfo_SetWeapon( pi, pi->weapon );
  1173.  
  1174.         jumpHeight = 0;
  1175.         pi->pendingLegsAnim = 0;
  1176.         UI_ForceLegsAnim( pi, legsAnim );
  1177.  
  1178.         pi->pendingTorsoAnim = 0;
  1179.         UI_ForceTorsoAnim( pi, torsoAnim );
  1180.  
  1181.         return;
  1182.     }
  1183.  
  1184.     // leg animation
  1185.     currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
  1186.     if ( legsAnim != LEGS_JUMP && ( currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND ) ) {
  1187.         pi->pendingLegsAnim = legsAnim;
  1188.     }
  1189.     else if ( legsAnim != currentAnim ) {
  1190.         jumpHeight = 0;
  1191.         pi->pendingLegsAnim = 0;
  1192.         UI_ForceLegsAnim( pi, legsAnim );
  1193.     }
  1194.  
  1195.     // torso animation
  1196.     if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2 ) {
  1197.         if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) {
  1198.             torsoAnim = TORSO_STAND2;
  1199.         }
  1200.         else {
  1201.             torsoAnim = TORSO_STAND;
  1202.         }
  1203.     }
  1204.  
  1205.     if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 ) {
  1206.         if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) {
  1207.             torsoAnim = TORSO_ATTACK2;
  1208.         }
  1209.         else {
  1210.             torsoAnim = TORSO_ATTACK;
  1211.         }
  1212.         pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH;
  1213.         //FIXME play firing sound here
  1214.     }
  1215.  
  1216.     currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
  1217.  
  1218.     if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP ) {
  1219.         pi->pendingTorsoAnim = torsoAnim;
  1220.     }
  1221.     else if ( ( currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK ) && ( torsoAnim != currentAnim ) ) {
  1222.         pi->pendingTorsoAnim = torsoAnim;
  1223.     }
  1224.     else if ( torsoAnim != currentAnim ) {
  1225.         pi->pendingTorsoAnim = 0;
  1226.         UI_ForceTorsoAnim( pi, torsoAnim );
  1227.     }
  1228. }
  1229.